#ifndef _CPU_H
#define _CPU_H

#include "type.h"
#include "nes_config.h"

// the 6502 CPU registers
struct R6502
{
  u8  A;
  u8  X;
  u8  Y;
  u8  S;
  u8  F;
  u16 PC;
};

class CNes;
class CMapper;
class CCpu
{
  friend CNes;
public:
                CCpu(CNes* nes);
                ~CCpu();

public:
  void          Init();
  void          InitRom(int mapper, int bankcount);
  void          CloseRom();
  void          CreateMapper(int mapper);
  void          Reset();
  void          Stop();
  int           NMI();
  int           Execute(int cycles);

public:
  typedef int (CCpu::*I0xXX)();
  int   I0x00(); int    I0x01(); int    I0x02(); int    I0x03();
  int   I0x04(); int    I0x05(); int    I0x06(); int    I0x07();
  int   I0x08(); int    I0x09(); int    I0x0A(); int    I0x0B();
  int   I0x0C(); int    I0x0D(); int    I0x0E(); int    I0x0F();
  int   I0x10(); int    I0x11(); int    I0x12(); int    I0x13();
  int   I0x14(); int    I0x15(); int    I0x16(); int    I0x17();
  int   I0x18(); int    I0x19(); int    I0x1A(); int    I0x1B();
  int   I0x1C(); int    I0x1D(); int    I0x1E(); int    I0x1F();
  int   I0x20(); int    I0x21(); int    I0x22(); int    I0x23();
  int   I0x24(); int    I0x25(); int    I0x26(); int    I0x27();
  int   I0x28(); int    I0x29(); int    I0x2A(); int    I0x2B();
  int   I0x2C(); int    I0x2D(); int    I0x2E(); int    I0x2F();
  int   I0x30(); int    I0x31(); int    I0x32(); int    I0x33();
  int   I0x34(); int    I0x35(); int    I0x36(); int    I0x37();
  int   I0x38(); int    I0x39(); int    I0x3A(); int    I0x3B();
  int   I0x3C(); int    I0x3D(); int    I0x3E(); int    I0x3F();
  int   I0x40(); int    I0x41(); int    I0x42(); int    I0x43();
  int   I0x44(); int    I0x45(); int    I0x46(); int    I0x47();
  int   I0x48(); int    I0x49(); int    I0x4A(); int    I0x4B();
  int   I0x4C(); int    I0x4D(); int    I0x4E(); int    I0x4F();
  int   I0x50(); int    I0x51(); int    I0x52(); int    I0x53();
  int   I0x54(); int    I0x55(); int    I0x56(); int    I0x57();
  int   I0x58(); int    I0x59(); int    I0x5A(); int    I0x5B();
  int   I0x5C(); int    I0x5D(); int    I0x5E(); int    I0x5F();
  int   I0x60(); int    I0x61(); int    I0x62(); int    I0x63();
  int   I0x64(); int    I0x65(); int    I0x66(); int    I0x67();
  int   I0x68(); int    I0x69(); int    I0x6A(); int    I0x6B();
  int   I0x6C(); int    I0x6D(); int    I0x6E(); int    I0x6F();
  int   I0x70(); int    I0x71(); int    I0x72(); int    I0x73();
  int   I0x74(); int    I0x75(); int    I0x76(); int    I0x77();
  int   I0x78(); int    I0x79(); int    I0x7A(); int    I0x7B();
  int   I0x7C(); int    I0x7D(); int    I0x7E(); int    I0x7F();
  int   I0x80(); int    I0x81(); int    I0x82(); int    I0x83();
  int   I0x84(); int    I0x85(); int    I0x86(); int    I0x87();
  int   I0x88(); int    I0x89(); int    I0x8A(); int    I0x8B();
  int   I0x8C(); int    I0x8D(); int    I0x8E(); int    I0x8F();
  int   I0x90(); int    I0x91(); int    I0x92(); int    I0x93();
  int   I0x94(); int    I0x95(); int    I0x96(); int    I0x97();
  int   I0x98(); int    I0x99(); int    I0x9A(); int    I0x9B();
  int   I0x9C(); int    I0x9D(); int    I0x9E(); int    I0x9F();
  int   I0xA0(); int    I0xA1(); int    I0xA2(); int    I0xA3();
  int   I0xA4(); int    I0xA5(); int    I0xA6(); int    I0xA7();
  int   I0xA8(); int    I0xA9(); int    I0xAA(); int    I0xAB();
  int   I0xAC(); int    I0xAD(); int    I0xAE(); int    I0xAF();
  int   I0xB0(); int    I0xB1(); int    I0xB2(); int    I0xB3();
  int   I0xB4(); int    I0xB5(); int    I0xB6(); int    I0xB7();
  int   I0xB8(); int    I0xB9(); int    I0xBA(); int    I0xBB();
  int   I0xBC(); int    I0xBD(); int    I0xBE(); int    I0xBF();
  int   I0xC0(); int    I0xC1(); int    I0xC2(); int    I0xC3();
  int   I0xC4(); int    I0xC5(); int    I0xC6(); int    I0xC7();
  int   I0xC8(); int    I0xC9(); int    I0xCA(); int    I0xCB();
  int   I0xCC(); int    I0xCD(); int    I0xCE(); int    I0xCF();
  int   I0xD0(); int    I0xD1(); int    I0xD2(); int    I0xD3();
  int   I0xD4(); int    I0xD5(); int    I0xD6(); int    I0xD7();
  int   I0xD8(); int    I0xD9(); int    I0xDA(); int    I0xDB();
  int   I0xDC(); int    I0xDD(); int    I0xDE(); int    I0xDF();
  int   I0xE0(); int    I0xE1(); int    I0xE2(); int    I0xE3();
  int   I0xE4(); int    I0xE5(); int    I0xE6(); int    I0xE7();
  int   I0xE8(); int    I0xE9(); int    I0xEA(); int    I0xEB();
  int   I0xEC(); int    I0xED(); int    I0xEE(); int    I0xEF();
  int   I0xF0(); int    I0xF1(); int    I0xF2(); int    I0xF3();
  int   I0xF4(); int    I0xF5(); int    I0xF6(); int    I0xF7();
  int   I0xF8(); int    I0xF9(); int    I0xFA(); int    I0xFB();
  int   I0xFC(); int    I0xFD(); int    I0xFE(); int    I0xFF();

private:
  /* Instructions used in cpu module */

  // Flag Operation
  void  SET_FLAG(u8 flag);
  void  CLEAR_FLAG(u8 flag);
  u8    CHECK_FLAG(u8 flag);
  void  SET_FLAG_CONDITION(bool c, u8 flag);
  void  SET_ZN_FLAG(u8 value);

  // Addressing Mode
  u16   GET_ADDR_IMMEDIATE();
  u16   GET_ADDR_ZEROPAGE();
  u16   GET_ADDR_ABSOLUTE(bool jsr = false);
  u16   GET_ADDR_ZEROPAGE_X();
  u16   GET_ADDR_ZEROPAGE_Y();
  u16   GET_ADDR_ABSOLUTE_X();
  u16   GET_ADDR_ABSOLUTE_Y();
  u16   GET_ADDR_PREINDEXED();
  u16   GET_ADDR_POSTINDEXED();
  u8    GET_DATA_IMMEDIATE();
  u8    GET_DATA_ZEROPAGE();
  u8    GET_DATA_ABSOLUTE(bool jsr = false);
  u8    GET_DATA_ZEROPAGE_X();
  u8    GET_DATA_ZEROPAGE_Y();
  u8    GET_DATA_ABSOLUTE_X();
  u8    GET_DATA_ABSOLUTE_Y();
  u8    GET_DATA_PREINDEXED();
  u8    GET_DATA_POSTINDEXED();

  // Stack Operation
  void  Push(u8 data);
  void  PushWord(u16 data);
  u8    Pop();
  u16   PopWord();

  // Instructions
  void  ADC(u8 data);
  void  AND(u8 data);
  void  ASL(u16 addr);
  void  ASL_A();
  void  BCC();
  void  BCS();
  void  BEQ();
  void  BIT(u8 data);
  void  BMI();
  void  BNE();
  void  BPL();
  void  BRK();
  void  BVC();
  void  BVS();
  void  CMP(u8 data);
  void  CPX(u8 data);
  void  CPY(u8 data);
  void  DEC(u16 addr);
  void  DEX();
  void  DEY();
  void  EOR(u8 data);
  void  INC(u16 addr);
  void  INX();
  void  INY();
  void  JMP(u16 addr);
  void  JSR();
  void  LDA(u8 data);
  void  LDX(u8 data);
  void  LDY(u8 data);
  void  LSR_A();
  void  LSR(u16 addr);
  void  ORA(u8 data);
  void  PHA();
  void  PHP();
  void  PLA();
  void  PLP();
  void  ROL_A();
  void  ROL(u16 addr);
  void  ROR_A();
  void  ROR(u16 addr);
  void  RTI();
  void  RTS();
  void  SBC(u8 data);
  void  STA(u16 addr);
  void  STX(u16 addr);
  void  STY(u16 addr);
  void  TAX();
  void  TAY();
  void  TSX();
  void  TXA();
  void  TXS();
  void  TYA();

private:
  void  JMP_IF(u8 c);

public:
  u8    GetNextOpcode(u16 addr);
  u16   GetCurPC();
  R6502&  GetContext();
  u8*   GetStack();

public:
  u8*   GetBuf(u16 addr);
  u8    ReadByte(u16 addr);
  u16   ReadWord(u16 addr);
  u16   ReadWordZeroPage(u8 addr);
  void  Write(u16 addr, u8 data);

  void  NMIReq();

public:
  R6502 m_Register;
  int   m_TotalCycle;

  u8*   m_Stack;
  u8    m_ZNTable[256];

  static I0xXX  m_ITable[0x100];

  /*
    CPU Memory Map (16bit buswidth, 0-FFFFh)
      0000h-07FFh   Internal 2K Work RAM (mirrored to 800h-1FFFh)
      2000h-2007h   Internal PPU Registers (mirrored to 2008h-3FFFh)
      4000h-4017h   Internal APU Registers
      4018h-5FFFh   Cartridge Expansion Area almost 8K
      6000h-7FFFh   Cartridge SRAM Area 8K
      8000h-FFFFh   Cartridge PRG-ROM Area 32K
  */
  u8  CPU_MEM[64*1024];
  u8* RAM;
  u8* ExpansionArea;
  u8* SRAM;
  u8* ROM;
  u8* PrgRom[PRG_BANK_COUNT];  // 4*8k prg rom
  int m_ROMBankCount;

  u8  WRAM[2 * 1024];
  u8  VRAM[2 * 1024];

private:
  CNes*     m_NES;
  CMapper*  m_Mapper;
  int       m_ExtraCycle;
};

#define ROM_BANK(i)   &m_Cpu->ROM[(i)*PRG_BANK_SIZE]
#define ROM_BANK_L(i) \
  &m_Cpu->ROM[m_Cpu->m_ROMBankCount*PRGPageSize-((i)+1)*PRG_BANK_SIZE]

__forceinline R6502&
CCpu::GetContext()
{
  return m_Register;
}
__forceinline u8*
CCpu::GetStack()
{
  return m_Stack;
}
__forceinline u16
CCpu::GetCurPC()
{
  return m_Register.PC;
}

#endif
